import random
import copy

import libtcodpy as libtcod

import splash
import menu
import utils

BALANCE = 0  # Hack

class Dice(object):
    def __init__(self, sides, number, mult=1, plus=0):
        self.sides = sides
        self.number = number
        self.mult = mult
        self.plus = plus

    def __str__(self):
        plus = ("%+d" % self.plus) if self.plus else ''
        mult = ("%d*" % self.plus) if self.mult != 1 else ''
        return "%s%dd%d%s" % (mult, self.number, self.sides, plus)

    def __int__(self):
        return self.roll()
    def __float__(self):
        return float(self.roll())

    def __nonzero__(self):
        return True

    def __cmp__(self, other):
        return cmp(self.roll(), other)
    def __gt__(self, target):
        return self.roll() > target
    def __ge__(self, target):
        return self.roll() >= target
    def __lt__(self, target):
        return self.roll() < target
    def __le__(self, target):
        return self.roll() <= target
    def __eq__(self, target):
        return self.roll() == target
    def __ne__(self, target):
        return self.roll() != target

    def __add__(self, other):
        return self.roll() + int(other)
    def __radd__(self, other):
        return self.roll() + int(other)
    def __mul__(self, other):
        return self.roll() * int(other)
    def __rmul__(self, other):
        return self.roll() * int(other)
    def __sub__(self, other):
        return self.roll() - int(other)
    def __rsub__(self, other):
        return int(other) - self.roll()
    def __div__(self, other):
        return self.roll() / int(other)
    def __rdiv__(self, other):
        return int(other) / self.roll()

    def __mod__(self, other):
        return self.roll() % other
    def __rmod__(self, other):
        return other % self.roll()

    def __neg__(self):
        return -self.roll()
    def __pos__(self):
        return self.roll()
    def __abs__(self):
        return abs(self.roll())

    # And that's all of the numeric object methods I can think of. :)

    def roll(self):
        if not (self.sides or self.number or self.mult):
            return 0
        result = (sum([random.randrange(self.sides) + 1 for i in range(self.number)]) + self.plus) * self.mult
        return int(result)


class Thing(object):
    def __init__(self, x, y, char, color, priority=50):
        self.x = x
        self.y = y
        self.char = char
        self.color = color

        self.name = 'generic object'
        self.short_description = "an instance of a generic object"
        self.long_description = "a much longer description of a generic object.  May be word wrapped."

        self.priority = priority  # High priority items are drawn later
        self.value = 0

        self.use_str = '[used]'
        self.equiped = False

    def __str__(self):
        if self.equiped:
            return ' '.join([self.name, self.use_str])
        else:
            return self.name

    def __cmp__(self, other):
        this = (self.priority, self.value, self.name)
        that = (other.priority, other.value, other.name)

        return cmp(this, that)

class Chomper(Thing):
    colors = libtcod.color_gen_map([libtcod.Color(255, 255, 255), libtcod.Color(255, 0, 0)], [0, 31])
    damage = (Dice(6, 1), Dice(8, 1), Dice(10, 1), Dice(6, 2))
    def __init__(self, x, y, level=1):
        sequence = list('/()\\')
        if x % 2:
            sequence.reverse()

        super(Chomper, self).__init__(x, y, sequence[0], Chomper.colors[0], priority=70)
        self.sequence = sequence
        self.blood = 0
        self.phase = 0
        self.level = level

        self.name = 'Chomper'
        self.splatter = []

    def clean(self):
        self.blood = 0
        self.color = Chomper.colors[0]

    def update(self):
        if self.blood:
            self.blood -= 1
            self.color = Chomper.colors[self.blood]

        self.phase = (self.phase + 1) % (len(self.sequence))
        self.char = self.sequence[self.phase]

    def attack(self, other):
        self.blood = len(Chomper.colors) - 1
        self.color = Chomper.colors[self.blood]
        for blade in self.splatter:
            blade.blood = min(len(Chomper.colors) - 1, blade.blood + len(Chomper.colors) / 2)
            blade.color = Chomper.colors[blade.blood]

        if isinstance(other, Player):
            return max(1, int(Chomper.damage[self.level - 1]) - other.ac)
            #return int(self.damage)
        else: # Chompers kill monsters in one hit
            return other.hp + 1

class Spawner(Thing):
    monsters = []
    def __init__(self, x, y, color, level=1):
        super(Spawner, self).__init__(x, y, libtcod.CHAR_RADIO_UNSET, color)
        self.level = level
        self.cool_down = 0
        self.name = 'Spawner'

        if not Spawner.monsters:
            Spawner.monsters = read_monsters('monsters.cfg')

    def update(self, dt):
        if self.cool_down > 0:
            self.cool_down -= dt
            if self.cool_down <= 0:
                self.cool_down = 0
                self.char = libtcod.CHAR_RADIO_UNSET

    def spawn(self, note, cool_down):
        self.char = libtcod.CHAR_RADIO_SET
        self.cool_down = cool_down
        stats = copy.copy(random.choice(Spawner.monsters))
        stats.update(dict(color=self.color*1.5, x=self.x, y=self.y+1, note=note))
        stats['hp'] = stats['hp'] * self.level

        return Monster(**stats)

class Monster(Thing):
    def __init__(self, x, y, char, color, note, hp, ac=10, priority=60, 
                 name=None, short_description=None, long_description=None,
                 notes=None):
        super(Monster, self).__init__(x, y, char, color, priority)
        self.hp = hp
        self.max_hp = hp
        self.ac = ac
        self.note = note

        self.to_hit = Dice(0, 0)
        self.damage = Dice(0, 0)

        if name is None:
            self.name = 'generic monster'
        else:
            self.name = name

        if short_description is None:
            self.short_description = "an instance of a %s" % self.name
        else:
            self.short_description = short_description

        if long_description is None:
            self.long_description = self.short_description
        else:
            self.long_description = long_description


        self.cool_down = 0

    def __repr__(self):
        return "<Monster %s @ (%d, %d) %r>" % (self.name, self.x, self.y, self.char)

    def attack(self, other):
        return None

    def kill(self):
        self.hp = 0
        self.color = libtcod.light_grey

    @property
    def alive(self):
        return self.hp > 0

class Player(Thing):
    def __init__(self, x, y, name, char='@', color=libtcod.white):
        super(Player, self).__init__(x, y, char, color, priority=100)
        self.name = name

        self.inventory = []
        self.gigs = dict()
        self.fame = 0    # a.total = 0
        self.gold = 100

        # Do something about this
        self.short_description = "our hero, %s" % self.name
        self.long_description = """this is %s.  A simple adventurer in a complicated world, but tonight,
for one heart stopping moment, quite possibly... a Hero.""".replace('\n', ' ')

        # Everyone gets an axe.
        default_weapon = Weapon(BALANCE, Dice(6, 1), 0)
        default_weapon.name = 'an axe'
        default_weapon.short_description = 'your trusty battle honed axe.'
        default_weapon.long_description = """an axe pulled from the cold dead hands of a kobold... or was
it a goblin.  Wait, wait, it was an orc.  Defiantly an orc.  It has a Drakkons'831 sticker on its face.
Man, the Drakkons absolutely plundered Moria that year.  Good times.  The rust just shows that it's
really been used.""".replace('\n', ' ')
        self.weapon = None
        self.wield(default_weapon)

        # Proper adventurers wear clothes.  No exceptions.
        default_armor = Armor(0, 0)
        default_armor.name = 'Steet clothes'
        default_armor.short_description = 'your basic jeans and T-shirt.'
        default_armor.long_description = """a pair of faded and torn jeans (extra tight), a black
concert shirt from Wolves Bane's short lived reunion tour before Uresh hooked up with Bellorith
and quit the band and old combat boots.""".replace('\n', ' ')
        self.armor = None
        self.wear(default_armor)

        # Ten HP should be enough for now.
        self.max_hp = 10
        self.hp = self.max_hp

        # Delay between actions
        self.cool_down = 0

        self.done = False

    def wield(self, weapon):
        assert hasattr(weapon, 'weapon')

        if self.weapon:
            self.weapon.equiped = False
            self.inventory.append(self.weapon)

        if weapon in self.inventory:
            self.inventory.pop(self.inventory.index(weapon))

        self.weapon = weapon
        self.to_hit = Dice(1, 20, plus=self.weapon.to_hit + BALANCE)
        self.damage = self.weapon.damage

        self.weapon.equiped = True

    def wear(self, armor):
        assert hasattr(armor, 'armor')

        if self.armor:
            self.armor.equiped = False
            self.inventory.append(self.armor)

        if armor in self.inventory:
            self.inventory.pop(self.inventory.index(armor))

        self.armor = armor
        self.ac = self.armor.ac

        self.armor.equiped = True

    def attack(self, other):
        if self.to_hit > other.ac:
            return int(self.damage)

class Armor(Thing):
    def __init__(self, ac, value, x=None, y=None, 
                 name=None, short_description=None, long_description=None,
                 char=libtcod.CHAR_CHECKBOX_UNSET, color=libtcod.yellow):
        super(Armor, self).__init__(x, y, char, color)
        self.ac = ac
        self.value = value

        self.armor = True

        if name is None:
            self.name = 'generic armor'
        else:
            self.name = name

        if short_description is None:
            self.short_description = "generic armor of genericness"
        else:
            self.short_description = short_description

        if long_description is None:
            self.long_description = "a Much longer description of generic armor.  May be word wrapped."
        else:
            self.long_description = long_description

        self.use_str = '[worn]'

class Weapon(Thing):
    def __init__(self, to_hit, damage, value, x=None, y=None, 
                 name=None, short_description=None, long_description=None,
                 char=libtcod.CHAR_SUBP_NE, color=libtcod.white):
        super(Weapon, self).__init__(x, y, char, color)
        self.to_hit = to_hit
        self.damage = damage
        self.value = value

        self.weapon = True

        if name is None:
            self.name = 'generic weapon'
        else:
            self.name = name

        if short_description is None:
            self.short_description = "generic weapon of genericness"
        else:
            self.short_description = short_description

        if long_description is None:
            self.long_description = "a Much longer description of generic weapon.  May be word wrapped."
        else:
            self.long_description = long_description

        self.use_str = '[wielded]'

class Shop(object):
    def __init__(self, name, inventory, exit_offset=None):
        self.name = name
        self.inventory = inventory

        if exit_offset is None:
            self.exit_offset = (0, 0)
        else:
            self.exit_offset = exit_offset

        self.welcome_msg = "Select the item you would like to buy and press enter."

    def shop(self, console, player):
        # Status show shop name and player money
        welcome_width = console.width * 2 / 3
        welcome = "Welcome to %s.  %s\nYou have %d gold" % (self.name, self.welcome_msg, player.gold)
        welcome_height = 2 + console.height_left_rect(0, 0, welcome_width - 2, console.height, welcome)
        status = splash.Splash(welcome, welcome_width, welcome_height, border=True, title = self.name,
                               wrap=True, key_hit=False, background_alpha=0.8)
        status_x, status_y = (console.width - welcome_width) / 2, 3
        status.render(console, x=status_x, y=status_y)

        # Menu selects item to buy
        owned = player.inventory + [player.weapon, player.armor]
        items = [item for item in self.inventory if item not in owned]
        items_width, items_height = console.width / 2, 10
        select_x, select_y = (console.width - items_width) / 2, status_y + welcome_height + 2

        # Callback for item description
        self.description = None
        def menu_item(item):
            description_text = "%s\nCost: %d gold." % (utils.desc_cap(item.long_description), item.value)
            description_width = console.width * 2 / 3
            description_height = 2 + console.height_left_rect(0, 0, description_width - 2,
                                                              console.height, description_text)
            if self.description:
                if (self.description.text == description_text):
                    return
                self.description.remove()  # Remove last description window
            self.description = splash.Splash(description_text, description_width, description_height, border=True,
                                             title=item.name, wrap=True, key_hit=False, background_alpha=0.8)
            x, y = (console.width - description_width) / 2, select_y + items_height + 2
            self.description.render(console, x=x, y=y)

        # Show menu
        select = menu.Menu(items_width, items_height, 'Inventory', items, 
                           footer='Press enter to buy', callback=menu_item)
        item = select.show(console, select_x, select_y, background_alpha=0.8)
        if self.description:
            self.description.remove()

        # Remove windows
        status.done = True
        status.render(console, x=status_x, y=status_y)

        # Add item if you can afford it
        if item is None:
            goodbye_text = "Thanks for browsing.  Come back when you want to buy something."
        elif item.value <= player.gold:
            player.gold -= item.value
            player.inventory.append(item)
            goodbye_text = "Thank you for your purchase."
        else:
            goodbye_text = "I'm sorry, we only take cash.  Come back when you have %d gold" % item.value
        goodbye_width = console.width * 2 / 3
        goodbye_height = 2 + console.height_left_rect(0, 0, goodbye_width - 2, console.height, goodbye_text)
        goodbye = splash.Splash(goodbye_text, goodbye_width, goodbye_height, border=True, title=self.name,
                                wrap=True, timeout=5, background_alpha=0.8)
        goodbye_x, goodbye_y = (console.width - goodbye_width) / 2, status_y
        goodbye.show(console, goodbye_x, goodbye_y)

        player.x += self.exit_offset[0]
        player.y += self.exit_offset[1]


class WeaponShop(Shop):
    def __init__(self, exit_offset=(0, 0), inventory_file='weapons.cfg'):
        super(WeaponShop, self).__init__("Weapon World", read_weapons(inventory_file), exit_offset)

class ArmorShop(Shop):
    def __init__(self, exit_offset=(0, 0), inventory_file='armor.cfg'):
        super(ArmorShop, self).__init__("Lucie's Leathers", read_armor(inventory_file), exit_offset)

def read_weapons(inventory_file):
    parser = libtcod.parser_new()
    weapon_type = libtcod.parser_new_struct(parser, 'weapon')
    libtcod.struct_add_property(weapon_type, "value", libtcod.TYPE_INT, True)
    libtcod.struct_add_property(weapon_type, "to_hit", libtcod.TYPE_INT, True)
    libtcod.struct_add_property(weapon_type, "short_description", libtcod.TYPE_STRING, True)
    libtcod.struct_add_property(weapon_type, "long_description", libtcod.TYPE_STRING, True)
    libtcod.struct_add_property(weapon_type, "damage", libtcod.TYPE_DICE, True)

    weapons = []
    class WeaponListener:
        def new_struct(self, struct, name):
            if libtcod.struct_get_name(struct) != 'weapon':
                return False
            self.weapon = dict(name=name)
            return True
        def new_flag(self, name):
            self.weapon['name'] = True
            return True
        def new_property(self, name, typ, value):
            if name == 'damage' and (typ == libtcod.TYPE_DICE):
                self.weapon['damage'] = Dice(value.nb_faces, value.nb_dices, value.multiplier, value.addsub)
            elif typ == libtcod.TYPE_STRING:
                self.weapon[name] = value
            elif typ == libtcod.TYPE_INT:
                self.weapon[name] = value
            else:
                return False
            return True
        def end_struct(self, struct, name):
            weapons.append(Weapon(**self.weapon))

            return True
        def error(self, msg):
            print "Error while reading %r" % inventory_file
            print str(msg)
            return True
    libtcod.parser_run(parser, inventory_file, WeaponListener())

    return weapons

def read_armor(inventory_file):
    parser = libtcod.parser_new()
    armor_type = libtcod.parser_new_struct(parser, 'armor')
    libtcod.struct_add_property(armor_type, "value", libtcod.TYPE_INT, True)
    libtcod.struct_add_property(armor_type, "ac", libtcod.TYPE_INT, True)
    libtcod.struct_add_property(armor_type, "short_description", libtcod.TYPE_STRING, True)
    libtcod.struct_add_property(armor_type, "long_description", libtcod.TYPE_STRING, True)

    armor = []
    class WeaponListener:
        def new_struct(self, struct, name):
            if libtcod.struct_get_name(struct) != 'armor':
                return False
            self.armor = dict(name=name)
            return True
        def new_flag(self, name):
            self.armor['name'] = True
            return True
        def new_property(self, name, typ, value):
            if typ == libtcod.TYPE_STRING:
                self.armor[name] = value
            elif typ == libtcod.TYPE_INT:
                self.armor[name] = value
            else:
                return False
            return True
        def end_struct(self, struct, name):
            armor.append(Armor(**self.armor))
            return True
        def error(self, msg):
            print "Error while reading %r" % inventory_file
            print str(msg)
            return True
    libtcod.parser_run(parser, inventory_file, WeaponListener())

    return armor

def read_monsters(monster_file):
    parser = libtcod.parser_new()
    # char, hp, ac, name, short_description
    monster_type = libtcod.parser_new_struct(parser, 'monster')
    libtcod.struct_add_property(monster_type, "char", libtcod.TYPE_CHAR, True)
    libtcod.struct_add_property(monster_type, "hp", libtcod.TYPE_INT, True)
    libtcod.struct_add_property(monster_type, "ac", libtcod.TYPE_INT, True)
    libtcod.struct_add_property(monster_type, "short_description", libtcod.TYPE_STRING, True)
    libtcod.struct_add_property(monster_type, "long_description", libtcod.TYPE_STRING, False)
            
    monsters = []
    class MonsterListener:
        def new_struct(self, struct, name):
            if libtcod.struct_get_name(struct) != 'monster':
                return False
            self.monster = dict(name=name, color=libtcod.white)
            return True
        def new_flag(self, name):
            self.monster['name'] = True
            return True
        def new_property(self, name, typ, value):
            if typ == libtcod.TYPE_STRING:
                self.monster[name] = value
            elif typ == libtcod.TYPE_INT:
                self.monster[name] = value
            elif typ == libtcod.TYPE_CHAR:
                self.monster[name] = value
            else:
                return False
            return True
        def end_struct(self, struct, name):
            monsters.append(self.monster)
            return True
        def error(self, msg):
            print "Error while reading %r" % monster_file
            print str(msg)
            return True
    libtcod.parser_run(parser, monster_file, MonsterListener())

    return monsters


if __name__ == '__main__':
    from pprint import pprint
#    pprint(read_weapons('weapons.cfg'))
#    pprint(read_armor('armor.cfg'))
#    pprint(read_monsters('monsters.cfg'))

    s = Spawner(10, 10, libtcod.white, 1)

    wave = []
    wave.append(s.spawn(None, 0))
    wave.append(s.spawn(None, 0))
    wave.append(s.spawn(None, 0))
    wave.append(s.spawn(None, 0))
    pprint( wave)



        

